package it.uniroma2.mpsr.algorithms;

import it.uniroma2.mpsr.resources.Centre;
import it.uniroma2.mpsr.resources.FPResults;
import it.uniroma2.mpsr.resources.State;
import it.uniroma2.mpsr.utils.Permutations;
import it.uniroma2.mpsr.utils.Misc;

import java.math.BigDecimal;
import java.math.RoundingMode;


public class FP {

	public static FPResults solve(double[][] routing, Centre[] centres, int N) {

		double[] weights = new double[centres.length];
		for(int i=0; i< centres.length; i++)
			weights[i] = centres[i].getMu();

		double[] x = Misc.solveSystem(routing, weights, 0, 1);

		if(x == null)
			return null;

		int Ns = (int) Misc.binomial(N + centres.length -1, centres.length - 1);

		Permutations p = new Permutations(Ns);
		p.generatePermutations(N, new int[centres.length], 0);
		State[] states = p.getStates();

		/*************************Calcolo G(N)**************************/
		BigDecimal gN = BigDecimal.ZERO;
		for(int i = 0; i < states.length; i++) {
			// calcolo produttoria dell'i-esimo centro
			states[i].setProduct(x, centres);
			gN = gN.add(states[i].getProduct());
		}
		/***************************************************************/

		/********************Probabilita marginali**********************/
		double[][] margProbs = new double[centres.length][N+1];
		for(int i = 0; i < states.length; i++) {
			double pi = states[i].getProduct().divide(gN, 30, RoundingMode.HALF_UP).doubleValue();
			states[i].setStatProb(pi);

			for(int j = 0; j < centres.length; j++) {
				int jobs = states[i].getElement(j);
				margProbs[j][jobs] += pi;
			}
		}
		/***************************************************************/

		/**********************Popolazioni medie************************/
		double[] meanPops = new double[centres.length];
		for(int i = 0; i < meanPops.length; i++) {
			for(int j = 1; j < margProbs[i].length; j++) {
				meanPops[i] += j * margProbs[i][j];
			}
		}
		/***************************************************************/

		/************************Utilizzazioni**************************/
		double[] rho = new double[centres.length];
		for(int i = 0; i < centres.length; i++) {
			if(centres[i].getKind().equals("SPC")) {
				int mi = centres[i].getMultiplicity();
				if(mi == 1) {
					rho[i] = 1 - margProbs[i][0];
				}
				else {

				}
				for(int j = 1; j < mi; j++) {
					rho[i] += j * margProbs[i][j];
				}
				rho[i] /= mi;
				for(int j = mi; j <= N; j++) {
					rho[i] += margProbs[i][j];
				}
			}
			else {
				for(int j = 1; j <= N; j++) {
					rho[i] += j * margProbs[i][j];
				}
				rho[i] /= N;
			}
		}
		/***************************************************************/

		/***************************Throughput**************************/
		double[] lambda = new double[centres.length];
		for(int i = 0; i < centres.length; i++) {
			lambda[i] = centres[i].getMultiplicity() * centres[i].getMu() * rho[i];
		}

		/********************Tempi medi di residenza********************/
		double[] residenceTimes = new double[centres.length];
		for(int i = 0; i < centres.length; i++) {
			residenceTimes[i] = meanPops[i] / lambda[i];
		}
		/***************************************************************/

		/*********************Rapporti tra visite***********************/
		double[][] v = new double[routing.length][routing.length];
		for(int i=0; i < routing.length; i++) {
			for(int j=0; j < routing.length; j++) {
				if(lambda[j] != 0)
					v[i][j] = lambda[i] / lambda[j];
			}
		}
		/***************************************************************/

		/********************Tempi medi di risposta*********************/
		double[] respTimes = new double[centres.length];
		for(int i = 0; i < centres.length; i++) {
			for(int j = 0; j < centres.length; j++) {
				if(i != j) {
					respTimes[i] += residenceTimes[j] * v[j][i];
				}
			}
		}
		/***************************************************************/

		return new FPResults(centres, states, margProbs, gN, meanPops, rho, lambda, residenceTimes, respTimes);
	}
}
